<!doctype html>
<!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">

  <script src="../../../webcomponentsjs/webcomponents.js"></script>
  <script src="../../../web-component-tester/browser.js"></script>

  <link rel="import" href="../../polymer.html">
  <link rel="import" href="gestures-elements.html">

</head>
<body>

  <script>

    suite('simulate events', function() {

      var app;

      setup(function() {
        app = document.createElement('x-app');
        document.body.appendChild(app);
      });

      teardown(function() {
        document.body.removeChild(app);
      });

      test('tap on x-foo and check localTarget and rootTarget', function() {
        var foo = app.$.foo;
        foo.dispatchEvent(new CustomEvent('click', {bubbles: true}));
        assert.equal(app._testLocalTarget, app, 'local target');
        assert.equal(app._testRootTarget, foo, 'root target');
      });

      test('tap on x-foo.div and check target info', function() {
        var foo = app.$.foo;
        var div = foo.$.div;
        div.dispatchEvent(new CustomEvent('click', {bubbles: true}));
        assert.equal(app._testLocalTarget, app, 'app local target');
        assert.equal(app._testRootTarget, div, 'app root target');
        assert.equal(foo._testLocalTarget, foo, 'foo local target');
        assert.equal(foo._testRootTarget, div, 'foo root target');
      });

      test('HTMLElement.click triggers tap', function() {
        // put the element off screen to prevent x/y weirdness from .click()
        app.style.cssText = 'position: absolute; left: -1000px; top: -1000px;';
        // make a mousedown *very* far away to tickle the distance check
        var ev = new CustomEvent('mousedown');
        ev.clientX = 1e8;
        ev.clientY = 1e8;
        app.dispatchEvent(ev);
        app.click();
        assert.equal(app._testLocalTarget, app, 'app local target');
        assert.equal(app._testRootTarget, app, 'app root target');
      });
    });

    suite('Event Setup and Teardown', function() {
      var outer, inner;
      suiteSetup(function() {
        outer = document.createElement('x-setup');
        inner = outer.$.inner;
      });

      suite('setup', function() {
        test('listeners block', function() {
          assert.equal(outer.__polymerGesturesTouchAction, 'none');
          var obj = outer.__polymerGestures;
          assert.equal(obj.mousedown.downup, 2, 'mousedown downup');
          assert.equal(obj.mousedown.track, 1, 'mousedown track');
          assert.equal(obj.mousedown.tap, 1, 'mousedown tap');
          assert.equal(obj.mousedown._count, 4, 'total mousedown');
          assert.equal(obj.touchstart.downup, 2, 'touchstart downup');
          assert.equal(obj.touchstart.tap, 1, 'touchstart tap');
          assert.equal(obj.touchstart.track, 1, 'touchstart track');
          assert.equal(obj.touchstart._count, 4, 'total touchstart');
          assert.equal(obj.touchmove.track, 1, 'touchmove track');
          assert.equal(obj.touchmove._count, 1, 'total touchmove');
          assert.equal(obj.touchend.downup, 2, 'touchend downup');
          assert.equal(obj.touchend.track, 1, 'touchend track');
          assert.equal(obj.touchend.tap, 1, 'touchend tap');
          assert.equal(obj.touchend._count, 4, 'total touchend');
        });

        test('on-*', function() {
          assert.equal(inner.__polymerGesturesTouchAction, 'none');
          var obj = inner.__polymerGestures;
          assert.equal(obj.mousedown.downup, 2, 'mousedown downup');
          assert.equal(obj.mousedown.track, 1, 'mousedown track');
          assert.equal(obj.mousedown.tap, 1, 'mousedown tap');
          assert.equal(obj.mousedown._count, 4, 'total mousedown');
          assert.equal(obj.touchstart.downup, 2, 'touchstart downup');
          assert.equal(obj.touchstart.tap, 1, 'touchstart tap');
          assert.equal(obj.touchstart.track, 1, 'touchstart track');
          assert.equal(obj.touchstart._count, 4, 'total touchstart');
          assert.equal(obj.touchmove.track, 1, 'touchmove track');
          assert.equal(obj.touchmove._count, 1, 'total touchmove');
          assert.equal(obj.touchend.downup, 2, 'touchend downup');
          assert.equal(obj.touchend.track, 1, 'touchend track');
          assert.equal(obj.touchend.tap, 1, 'touchend tap');
          assert.equal(obj.touchend._count, 4, 'total touchend');
        });

        test('dynamic', function() {
          var el = document.createElement('x-dynamic');
          var obj = el.__polymerGestures;
          assert(!obj);
          el.setup();
          obj = el.__polymerGestures;
          assert(obj, 'gestures object exists');
          assert.equal(obj.mousedown.tap, 1, 'mousedown tap');
          assert.equal(obj.click.tap, 1, 'click tap');
          assert.equal(obj.touchstart.tap, 1, 'touchstart tap');
          assert.equal(obj.touchend.tap, 1, 'touchend tap');
        });
      });

      suite('teardown', function() {
        test('listeners block', function(){
          var events = Object.keys(outer.listeners);
          events.forEach(function(ev) {
            outer.unlisten(outer, ev, outer.listeners[ev]);
          });
          assert.equal(outer.__polymerGesturesTouchAction, 'none');
          var obj = outer.__polymerGestures;
          assert.equal(obj.mousedown.downup, 0, 'mousedown downup');
          assert.equal(obj.mousedown.track, 0, 'mousedown track');
          assert.equal(obj.mousedown.tap, 0, 'mousedown tap');
          assert.equal(obj.mousedown._count, 0, 'total mousedown');
          assert.equal(obj.touchstart.downup, 0, 'touchstart downup');
          assert.equal(obj.touchstart._count, 0, 'total touchstart');
          assert.equal(obj.touchmove.track, 0, 'touchmove track');
          assert.equal(obj.touchmove._count, 0, 'total touchmove');
          assert.equal(obj.touchstart.tap, 0, 'touchstart tap');
          assert.equal(obj.touchend.downup, 0, 'touchend downup');
          assert.equal(obj.touchend.track, 0, 'touchend track');
          assert.equal(obj.touchend.tap, 0, 'touchend tap');
          assert.equal(obj.touchend._count, 0, 'total touchend');
        });

        test('on-*', function(){
          var innerNotes = outer._notes[0];
          innerNotes.events.forEach(function(no) {
            outer.unlisten(inner, no.name, no.value);
          });
          assert.equal(inner.__polymerGesturesTouchAction, 'none');
          var obj = inner.__polymerGestures;
          assert.equal(obj.mousedown.downup, 0, 'mousedown downup');
          assert.equal(obj.mousedown.track, 0, 'mousedown track');
          assert.equal(obj.mousedown.tap, 0, 'mousedown tap');
          assert.equal(obj.mousedown._count, 0, 'total mousedown');
          assert.equal(obj.touchstart.downup, 0, 'touchstart downup');
          assert.equal(obj.touchstart._count, 0, 'total touchstart');
          assert.equal(obj.touchmove.track, 0, 'touchmove track');
          assert.equal(obj.touchmove._count, 0, 'total touchmove');
          assert.equal(obj.touchstart.tap, 0, 'touchstart tap');
          assert.equal(obj.touchend.downup, 0, 'touchend downup');
          assert.equal(obj.touchend.track, 0, 'touchend track');
          assert.equal(obj.touchend.tap, 0, 'touchend tap');
          assert.equal(obj.touchend._count, 0, 'total touchend');
        });

        test('dynamic', function(){
          var el = document.createElement('x-dynamic');
          var obj = el.__polymerGestures;
          assert(!obj);
          el.setup();
          el.teardown();
          obj = el.__polymerGestures;
          assert(obj, 'gestures object exists');
          assert.equal(obj.mousedown.tap, 0, 'mousedown tap');
          assert.equal(obj.click.tap, 0, 'click tap');
          assert.equal(obj.touchstart.tap, 0, 'touchstart tap');
          assert.equal(obj.touchend.tap, 0, 'touchend tap');
        });
      });
    });

    suite('target finding', function() {
      var div, divLocation;

      setup(function() {
        div = document.createElement('div');
        div.style.cssText = 'height: 50px; width: 50px; background: red;';
        div.id = 'target';
        document.body.appendChild(div);
        divLocation = div.getBoundingClientRect();
      });

      test('target finding returns null outside the window', function() {
        var actual = Polymer.Gestures.deepTargetFind(-1, -1);
        assert.equal(actual, null);
      });

      test('find the div in document', function() {
        var x = divLocation.left, y = divLocation.top;
        var actual = Polymer.Gestures.deepTargetFind(x, y);
        assert.equal(actual, div);
      });

      test('find the div with a shadowroot', function() {
        if (!div.createShadowRoot) {
          return;
        }
        div.createShadowRoot();
        var x = divLocation.left, y = divLocation.top;
        var actual = Polymer.Gestures.deepTargetFind(x, y);
        assert.equal(actual, div);
      });

      test('find the div inside a shadowroot', function() {
        if (!div.createShadowRoot) {
          return;
        }
        var divOwner = document.createElement('span');
        document.body.appendChild(divOwner);
        divOwner.createShadowRoot().appendChild(div);
        var bcr = divOwner.getBoundingClientRect();
        var x = bcr.left, y = bcr.top;
        var actual = Polymer.Gestures.deepTargetFind(x, y);
        assert.equal(actual, div);
      });

      test('find the div with a shadowroot inside a shadowroot', function() {
        if (!div.createShadowRoot) {
          return;
        }
        div.createShadowRoot();
        var divOwner = document.createElement('span');
        document.body.appendChild(divOwner);
        divOwner.createShadowRoot().appendChild(div);
        var bcr = divOwner.getBoundingClientRect();
        var x = bcr.left, y = bcr.top;
        var actual = Polymer.Gestures.deepTargetFind(x, y);
        assert.equal(actual, div);
      });
    });

    suite('Prevention', function() {
      var el;
      setup(function() {
        el = document.createElement('x-prevent');
        document.body.appendChild(el);
      });
      teardown(function() {
        el.parentNode.removeChild(el);
        Polymer.Gestures.resetMouseCanceller();
      });

      test('tap', function() {
        var ev = new CustomEvent('mousedown', {
          bubbles: true,
          cancelable: true
        });
        el.dispatchEvent(ev);
        assert.equal(el.stream.length, 1, 'one event dispatched');
        assert.equal(el.stream[0].type, 'down', 'was down event');
        assert.equal(el.stream[0].defaultPrevented, true, 'was prevented');
        assert.equal(ev.defaultPrevented, true, 'base event was prevented');
      });

      test('track', function() {
        var ev = new CustomEvent('mousedown', {
          bubbles: true,
          cancelable: true
        });
        ev.clientX = ev.clientY = 0;
        el.dispatchEvent(ev);
        assert.equal(el.stream.length, 1);
        for (var i = 0; i < 10; i++) {
          ev = new CustomEvent(
            i === 9 ? 'mouseup' : 'mousemove',
            {bubbles: true, cancelable: true}
          );
          ev.clientX = ev.clientY = 10 * i;
          el.dispatchEvent(ev);
        }
        assert.equal(el.stream.length, 2, 'expected only down and up');
        assert.equal(el.stream[0].type, 'down', 'down was found');
        assert.equal(el.stream[0].defaultPrevented, true, 'down was prevented');
        assert.equal(el.stream[1].type, 'up', 'up was found');
      });

      test('nested track and tap with touch', function() {
        el.parentNode.removeChild(el);
        el = document.createElement('x-nested-prevent');
        var child = el.$.child;
        document.body.appendChild(el);
        var options = {bubbles: true, cancelable: true};

        var bgr = el.getBoundingClientRect();
        var clientX = bgr.left + (bgr.width / 2);
        var clientY = bgr.top + (bgr.bottom / 2);
        var ev = new CustomEvent('touchstart', options);
        ev.touches = ev.changedTouches = [
          {
            clientX: clientX,
            clientY: clientY,
            identifier: 1,
            target: child
          }
        ];
        ev.clientX = clientX;
        ev.clientY = clientY;
        child.dispatchEvent(ev);

        for (var i = 0; i < 10; i++) {
          clientX += 1;
          ev = new CustomEvent(i === 9 ? 'touchend' : 'touchmove', options);
          ev.touches = ev.changedTouches = [
            {
              clientX: clientX,
              clientY: clientY,
              identifier: 1,
              target: child
            }
          ];
          ev.clientX = clientX;
          ev.clientY = clientY;
          // tell gestures to not turn off mouse events
          child.dispatchEvent(ev);
        }

        assert.equal(child.stream.length, 0, 'expected no taps on the child');
        assert.notEqual(el.stream.length, 0, 'expected some tracks on the parent');
      });
    });

    suite('Buttons', function() {
      var el;

      setup(function() {
        el = document.createElement('x-buttons');
        document.body.appendChild(el);
      });

      teardown(function() {
        el.parentNode.removeChild(el);
      });

      suite('Down and Up', function() {
        test('Left Mouse Button Only', function() {
          var options = {bubbles: true};
          var evLeftDown = new CustomEvent('mousedown', options);
          // left button
          evLeftDown.button = 0;
          evLeftDown.clientX = 1;
          var evLeftUp = new CustomEvent('mouseup', options);
          var evRightDown = new CustomEvent('mousedown', options);
          // right button
          evRightDown.button = 2;
          evRightDown.clientX = 2;
          var evRightUp = new CustomEvent('mouseup', options);

          el.dispatchEvent(evLeftDown);
          el.dispatchEvent(evLeftUp);
          el.dispatchEvent(evRightDown);
          el.dispatchEvent(evRightUp);

          assert.equal(el.stream.length, 2, 'only saw one up and down pair');
          assert.equal(el.stream[0].type, 'down');
          assert.equal(el.stream[1].type, 'up');
          assert.equal(el.stream[0].detail.x, 1, 'only from the left button');
        });

        test('Recover from right click', function() {
          var options = {bubbles: true};
          var evDown = new CustomEvent('mousedown', options);
          var evMove = new CustomEvent('mousemove', options);
          evMove.buttons = 0;
          var evUp = new CustomEvent('mouseup', options);

          el.dispatchEvent(evDown);
          el.dispatchEvent(evMove);
          el.dispatchEvent(evUp);

          assert.equal(el.stream.length, 2, 'always get an up');
        });
      });

      suite('Tap', function() {
        test('Left Mouse Button Only', function() {
          var evMid = new CustomEvent('click', {bubbles: true});
          evMid.button = 1;
          var evLeft = new CustomEvent('click', {bubbles: true});
          evLeft.button = 0;

          el.dispatchEvent(evMid);
          el.dispatchEvent(evLeft);

          assert.equal(el.stream.length, 1, 'only one tap');
        });
      });

      suite('Track', function() {
        test('Left Mouse Button Only', function() {
          var options = {bubbles: true};
          var ev = new CustomEvent('mousedown', options);
          ev.clientX = ev.clientY = 0;
          el.dispatchEvent(ev);
          for (var i = 0; i < 5; i++) {
            ev = new CustomEvent('mousemove', options);
            ev.clientX = 10 * i;
            ev.clientY = 10 * i;
            // left button until move 4
            ev.buttons = (i > 3) ? 2 : 1;
            el.dispatchEvent(ev);
          }
          el.dispatchEvent(new CustomEvent('mouseup', options));

          // down, <skipped>, track:start, track:track, track:track, track:end, up
          assert.equal(el.stream.length, 6);
          assert.equal(el.stream[0].type, 'down');
          assert.equal(el.stream[1].detail.state, 'start');
          assert.equal(el.stream[2].detail.state, 'track');
          assert.equal(el.stream[3].detail.state, 'track');
          assert.equal(el.stream[4].type, 'up');
          assert.equal(el.stream[5].detail.state, 'end');
        });
      });
    });

    suite('SD Polyfill', function() {
      var el;
      setup(function() {
        el = document.createElement('x-document-listener');
        document.body.appendChild(el);
        el.setup();
      });

      teardown(function() {
        el.teardown();
        document.body.removeChild(el);
      });

      test('document listener works in SD polyfill', function() {
        var ev = new CustomEvent('mousedown', {bubbles: true});
        el.dispatchEvent(ev);
        assert.equal(el.stream.length, 1);
      });
    });

    suite('Reference Cleanup', function() {
      var el;

      setup(function() {
        el = document.createElement('x-buttons');
        document.body.appendChild(el);
      });

      teardown(function() {
        document.body.removeChild(el);
      });

      test('down and up clear document tracking', function() {
        var ev = new CustomEvent('mousedown', {bubbles: true});
        el.dispatchEvent(ev);

        // some recognizers do not track the document, like tap
        var recognizers = Polymer.Gestures.recognizers.filter(function(r) {
          return r.info.hasOwnProperty('movefn') &&
            r.info.hasOwnProperty('upfn');
        });

        assert.isAbove(recognizers.length, 0, 'some recognizers track the document');

        recognizers.forEach(function(r) {
          assert.isFunction(r.info.movefn, r.name + ' movefn');
          assert.isFunction(r.info.upfn, r.name + ' upfn');
        });

        ev = new CustomEvent('mouseup', {bubbles: true});
        el.dispatchEvent(ev);

        recognizers.forEach(function(r) {
          assert.isNull(r.info.movefn, r.name + ' movefn');
          assert.isNull(r.info.upfn, r.name + ' upfn');
        });
      });

      suite('Interop', function() {
        teardown(function() {
          Polymer.Gestures.resetMouseCanceller();
        });
        function tap(target) {
          var options = {bubbles: true, cancelable: true};
          ['touchstart', 'touchmove', 'touchend'].forEach(function(n) {
            var ev = new CustomEvent(n, options);
            ev.touches = ev.changedTouches = [
              {
                clientX: 0,
                clientY: 0,
                identifier: 1,
                target: target
              }
            ];
            ev.clientX = 0;
            ev.clientY = 0;
            target.dispatchEvent(ev);
          });
          ['mousedown', 'mousemove', 'mouseup', 'click'].forEach(function(n) {
            var ev = new CustomEvent(n, options);
            ev.button = 0;
            ev.buttons = 1;
            target.dispatchEvent(ev);
          });
        }
        test('elements without gestures click reliably', function() {
          var el = document.createElement('x-no-gesture');
          document.body.appendChild(el);
          CustomElements.takeRecords();
          tap(el.$.one);
          tap(el.$.two);
          assert.equal(el.stream.length, 2);
        });
      });
    });
  </script>

</body>
</html>
